// clang-format off
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
#include "traps.h"
#include "spinlock.h"
// clang-format on

// Interrupt descriptor table (shared by all CPUs).
struct gatedesc idt[256];
extern uint vectors[]; // in vectors.S: array of 256 entry pointers
struct spinlock tickslock;
uint ticks;

void tvinit(void) {
    int i;

    for (i = 0; i < 256; i++)
        SETGATE(idt[i], 0, SEG_KCODE << 3, vectors[i], 0);
    SETGATE(idt[T_SYSCALL], 1, SEG_KCODE << 3, vectors[T_SYSCALL], DPL_USER);

    initlock(&tickslock, "time");
}

void idtinit(void) { lidt(idt, sizeof(idt)); }

#define STACK_PUSH(off, val) *(uint *)(tf->esp - (off)) = (val);

static void do_alarm(struct trapframe *tf) {
    struct proc *curproc = myproc();

    if (curproc == 0 || (tf->cs & 3) != 3 || curproc->alarm_totTick <= 0 ||
        curproc->alarm_flagInHandler) {
        return;
    }

    if (curproc->alarm_flagInHandler == 0) {
        curproc->alarm_curTick++;
    }

    if (curproc->alarm_curTick >= curproc->alarm_totTick) {
        curproc->alarm_curTick = 0;
        curproc->alarm_flagInHandler = 1;

        // DEBUG
        // cprintf("Before alarm :\n");
        // cprintf("\t%%esp = %x\n", tf->esp);
        // cprintf("\t%%eip = %x @%p\n", tf->eip, tf->esp - 0x04);

        // cprintf("\t%%eax = %x @%p\n", tf->eax, tf->esp - 0x08);
        // cprintf("\t%%edx = %x @%p\n", tf->edx, tf->esp - 0x0c);
        // cprintf("\t%%ecx = %x @%p\n", tf->ecx, tf->esp - 0x10);

        // cprintf("\t%%ebx = %x @%p\n", tf->ebx, tf->esp - 0x14);
        // cprintf("\t%%ebp = %x @%p\n", tf->ebp, tf->esp - 0x18);
        // cprintf("\t%%esi = %x @%p\n", tf->esi, tf->esp - 0x1c);
        // cprintf("\t%%edi = %x @%p\n", tf->edi, tf->esp - 0x20);

        /* 保存寄存器 */

        // 指令指针
        STACK_PUSH(0x04, tf->eip);

        // 调用者保存寄存器
        // eax, edx, ecx
        STACK_PUSH(0x08, tf->eax);
        STACK_PUSH(0x0c, tf->edx);
        STACK_PUSH(0x10, tf->ecx);

        // 被调用者保存寄存器
        // ebx, ebp, esi, edi
        STACK_PUSH(0x14, tf->ebx);
        STACK_PUSH(0x18, tf->ebp);
        STACK_PUSH(0x1c, tf->esi);
        STACK_PUSH(0x20, tf->edi);

        /* 插入系统调用 */

        /**
         * movl SYS_alarm_warpper, %eax
         * add hardcode_len, %esp
         * int $0x40
         * ret
         *
         * b8 18 00 00 00
         * 83 c4 0c
         * cd 40
         * c3
         *
         * 00 c3 40 cd
         * 0c c4 83 00
         * 00 00 18 b8
         */

        STACK_PUSH(0x24, 0x00c340cd);
        STACK_PUSH(0x28, 0x0cc48300);
        STACK_PUSH(0x2c, 0x000018b8);

        /* 返回地址指向栈上代码 */
        STACK_PUSH(0x30, tf->esp - 0x2c);

        /* 更新用户栈 */
        tf->esp -= 0x30;

        /* 触发 alarm_handeler */
        tf->eip = (uint)curproc->alarm_handler;
    }
}

#undef STACK_PUSH

extern int mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm);

static int do_malloc(struct trapframe *tf) {
    // DEBUG
    // cprintf("DEBUG: Try malloc:\n");
    // cprintf("    %%eip = 0x%x\n", tf->eip);
    // cprintf("    Addr = 0x%x\n", rcr2());
    // cprintf("    Ps   = 0x%x\n", PGROUNDUP(rcr2()));
    // cprintf("    Sz   = 0x%x\n", myproc()->sz);

    // 校验位置
    uint addr = rcr2();
    if (addr >= myproc()->sz) {
        myproc()->killed = 1;
        return -1;
    }

    // 申请内存
    char *mem = kalloc();
    if (mem == 0) {
        cprintf("Error: out of memory");
        myproc()->killed = 1;
        return -1;
    }
    memset(mem, 0, PGSIZE);

    // 映射内存
    void *page_start = (void *)PGROUNDDOWN(rcr2());
    uint pmem = (uint)V2P(mem);
    if (mappages(myproc()->pgdir, page_start, PGSIZE, pmem, PTE_W | PTE_U) < 0) {
        cprintf("Error: out of memory (2)");
        myproc()->killed = 1;
        return -1;
    }

    return 0;
}

// PAGEBREAK: 41
void trap(struct trapframe *tf) {
    if (tf->trapno == T_SYSCALL) {
        if (myproc()->killed)
            exit();
        myproc()->tf = tf;
        syscall();
        if (myproc()->killed)
            exit();
        return;
    }

    switch (tf->trapno) {
    case T_IRQ0 + IRQ_TIMER:
        if (cpuid() == 0) {
            acquire(&tickslock);
            ticks++;
            wakeup(&ticks);
            release(&tickslock);
        }

        // 检查 alarm
        do_alarm(tf);

        lapiceoi();
        break;
    case T_IRQ0 + IRQ_IDE:
        ideintr();
        lapiceoi();
        break;
    case T_IRQ0 + IRQ_IDE + 1:
        // Bochs generates spurious IDE1 interrupts.
        break;
    case T_IRQ0 + IRQ_KBD:
        kbdintr();
        lapiceoi();
        break;
    case T_IRQ0 + IRQ_COM1:
        uartintr();
        lapiceoi();
        break;
    case T_IRQ0 + 7:
    case T_IRQ0 + IRQ_SPURIOUS:
        cprintf("cpu%d: spurious interrupt at %x:%x\n", cpuid(), tf->cs, tf->eip);
        lapiceoi();
        break;

    // 检查是否为延迟分配导致
    case T_PGFLT:
        do_malloc(tf);
        break;

    // PAGEBREAK: 13
    default:
        if (myproc() == 0 || (tf->cs & 3) == 0) {
            // In kernel, it must be our mistake.
            cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n", tf->trapno, cpuid(),
                    tf->eip, rcr2());
            panic("trap");
        }

        // In user space, assume process misbehaved.
        cprintf("pid %d %s: trap %d err %d on cpu %d "
                "eip 0x%x addr 0x%x--kill proc\n",
                myproc()->pid, myproc()->name, tf->trapno, tf->err, cpuid(), tf->eip, rcr2());
        myproc()->killed = 1;
    }

    // Force process exit if it has been killed and is in user space.
    // (If it is still executing in the kernel, let it keep running
    // until it gets to the regular system call return.)
    if (myproc() && myproc()->killed && (tf->cs & 3) == DPL_USER)
        exit();

    // Force process to give up CPU on clock tick.
    // If interrupts were on while locks held, would need to check nlock.
    if (myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0 + IRQ_TIMER)
        yield();

    // Check if the process has been killed since we yielded
    if (myproc() && myproc()->killed && (tf->cs & 3) == DPL_USER)
        exit();
}
